package no.sachmuld.methodcombination.api.reflection.component;

import no.sachmuld.methodcombination.api.BehaviourChain;
import no.sachmuld.methodcombination.api.BehaviourMethod;
import no.sachmuld.methodcombination.api.Direction;

import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.ArrayList;

/**
 *
 */
public abstract class SimpleMethodCombination<B, R, T> extends DelegatingMethodCombination<B, R> {

    private Direction direction = Direction.BOTTOM_UP;
    private boolean disableAroundMethods = false;
    private Class aroundBehaviourClass;
    private String primaryMethodName;


    @Override
    public SimpleMethodCombination<B, R, T> build(final Class behaviourClass, Method targetMethod) {

        if (primaryMethodName == null) {
            primaryMethodName = targetMethod.getName();
        }

        if (aroundBehaviourClass == null) {
            aroundBehaviourClass = behaviourClass;
        }

        final PrimaryMethodCombination primary = new PrimaryMethodCombination();
        buildChildren(behaviourClass, targetMethod, primary);

        if (disableAroundMethods) {

            executedBy(primary);

        } else {

            final AroundMethodCombination<B, R> around = new AroundMethodCombination<>(primaryMethodName, direction);
            buildChildren(aroundBehaviourClass, targetMethod, around);

            executedBy(around.inner(primary));

        }

        return this;

    }

    public SimpleMethodCombination<B, R, T> setDirection(Direction direction) {
        this.direction = direction;
        return this;
    }

    public SimpleMethodCombination<B, R, T> setAroundBehaviourClass(Class aroundBehaviourClass) {
        this.aroundBehaviourClass = aroundBehaviourClass;
        return this;
    }

    public SimpleMethodCombination<B, R, T> setPrimaryMethodName(String primaryMethodName) {
        this.primaryMethodName = primaryMethodName;
        return this;
    }

    public SimpleMethodCombination<B, R, T> setDisableAroundMethods(boolean disableAroundMethods) {
        this.disableAroundMethods = disableAroundMethods;
        return this;
    }

    protected void configurePrimaryMethod(BehaviourMethod.Builder builder) {}

    protected abstract boolean handleSingleResult(T singleResult);
    protected abstract R combinedResult();

    private class PrimaryMethodCombination extends DelegatingMethodCombination {
        @Override
        public PrimaryMethodCombination build(final Class behaviourClass, Method targetMethod) {

            BehaviourMethod.Builder primaryMethodBuilder = new BehaviourMethod.Builder(behaviourClass, targetMethod, true).name(primaryMethodName);
            configurePrimaryMethod(primaryMethodBuilder);
            final BehaviourMethod primaryMethod = primaryMethodBuilder.build();

            validatedAgainst(primaryMethod);

            executedBy(new AbstractMethodCombination<B, R>() {
                @Override
                public R execute(Object target, BehaviourChain<? extends B> behaviourChain, Object... parameters) {
                    for (Object behaviour : behaviourChain.iterable(direction)) {
                        if (behaviourClass.isAssignableFrom(behaviour.getClass())) {
                            T result = (T) primaryMethod.invoke(behaviour, parameters);
                            boolean doContinue = handleSingleResult(result);
                            if (!doContinue) {
                                break;
                            }
                        }
                    }
                    return combinedResult();
                }
            });

            return this;


        }
    }

}
